
#include "stdafx.h"


nlmtool_params_t nlmtool_params;


struct nlmtool_network_t
{
	nlmtool_network_t( INetwork* net = nullptr, long idx = -1 )
	{
		init( net, idx );
	}

	~nlmtool_network_t( )
	{
		if( description )
			SysFreeString( description );

		if( name )
			SysFreeString( name );

		if( network )
			network->Release( );
	}


	bool init( INetwork* net, long idx = -1 );

	void print( );

	bool set_category( NLM_NETWORK_CATEGORY new_category )
	{
		return S_OK == network->SetCategory( new_category );
	}

	bool set_name( wchar_t* new_name )
	{
		return S_OK == network->SetName( ( BSTR ) new_name );
	}

	bool set_descripiton( wchar_t* descripiton )
	{
		return S_OK == network->SetDescription( ( BSTR ) descripiton );
	}


	INetwork* network;

	long index;

	GUID guid;
	static const size_t guid_string_size = 64;
	wchar_t guid_string[ guid_string_size ];

	DWORD connectivity;
	NLM_NETWORK_CATEGORY category;
	NLM_DOMAIN_TYPE domain_type;

	BSTR name;
	BSTR description;

	SYSTEMTIME datetime_created;
	SYSTEMTIME datetime_connected;
};


bool nlmtool_network_t::init( INetwork* net, long idx )
{
	network = net;

	index = idx;

	if( network == nullptr )
	{
		guid = { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } };
		guid_string[ 0 ] = L'\0';

		connectivity = NLM_CONNECTIVITY_DISCONNECTED;
		category = NLM_NETWORK_CATEGORY_PUBLIC;
		domain_type = NLM_DOMAIN_TYPE_NON_DOMAIN_NETWORK;

		name = nullptr;
		description = nullptr;

		datetime_created = { 0, 0, 0, 0, 0, 0, 0, 0 };
		datetime_connected = { 0, 0, 0, 0, 0, 0, 0, 0 };

		return false;
	}


	size_t errors_count = 0;


	HRESULT hr = network->GetNetworkId( &guid );

	if( hr != S_OK )
	{
		fwprintf( stderr, L"INetwork::GetNetworkId( ) failed with error code (%d)!\n", hr );

		++errors_count;
	}
	else if( StringFromGUID2( guid, guid_string, guid_string_size ) == 0 )
	{
		fwprintf( stderr, L"StringFromGUID2( ) failed!\n" );

		++errors_count;
	}


	NLM_CONNECTIVITY native_connectivity;

	hr = network->GetConnectivity( &native_connectivity );

	if( hr != S_OK )
	{
		fwprintf( stderr, L"INetwork::GetConnectivity( ) failed with error code (%d)!\n", hr );

		++errors_count;
	}
	else
	{
		if( native_connectivity == NLM_CONNECTIVITY_DISCONNECTED )
			connectivity = ConnectivityDisconnected;
		else
			connectivity = native_connectivity;
	}

	
	hr = network->GetCategory( &category );

	if( hr != S_OK )
	{
		fwprintf( stderr, L"INetwork::GetCategory( ) failed with error code (%d)!\n", hr );

		++errors_count;
	}


	hr = network->GetDomainType( &domain_type );

	if( hr != S_OK )
	{
		fwprintf( stderr, L"INetwork::GetDomainType( ) failed with error code (%d)!\n", hr );

		++errors_count;
	}


	hr = network->GetName( &name );

	if( hr != S_OK )
	{
		fwprintf( stderr, L"INetwork::GetName( ) failed with error code (%d)!\n", hr );

		++errors_count;
	}


	hr = network->GetDescription( &description );

	if( hr != S_OK )
	{
		fwprintf( stderr, L"INetwork::GetDescription( ) failed with error code (%d)!\n", hr );

		++errors_count;
	}

	FILETIME native_datetime_created;
	FILETIME native_datetime_connected;

	hr = network->GetTimeCreatedAndConnected(
		&native_datetime_created.dwLowDateTime,
		&native_datetime_created.dwHighDateTime,
		&native_datetime_connected.dwLowDateTime,
		&native_datetime_connected.dwHighDateTime
	);

	if( hr != S_OK )
	{
		fwprintf( stderr, L"INetwork::GetTimeCreatedAndConnected( ) failed with error code (%d)!\n", hr );

		++errors_count;
	}
	else
	{
		if( FileTimeToSystemTime( &native_datetime_created, &datetime_created ) == 0 )
		{
			fwprintf( stderr, L"FileTimeToSystemTime( ) failed with error code (%d)!\n", GetLastError( ) );

			++errors_count;
		}

		if( FileTimeToSystemTime( &native_datetime_connected, &datetime_connected ) == 0 )
		{
			fwprintf( stderr, L"FileTimeToSystemTime( ) failed with error code (%d)!\n", GetLastError( ) );

			++errors_count;
		}
	}

	return errors_count == 0;
}


void print_connectivity( DWORD connectivity, wchar_t* prefix = L"" )
{
	wprintf_s( L"%s", prefix );

	if( connectivity & ConnectivityDisconnected )
		wprintf( L" DISCONNECTED" );

	if( connectivity & NLM_CONNECTIVITY_IPV4_NOTRAFFIC )
		wprintf( L" IPV4_NOTRAFFIC" );
	if( connectivity & NLM_CONNECTIVITY_IPV4_SUBNET )
		wprintf( L" IPV4_SUBNET" );
	if( connectivity & NLM_CONNECTIVITY_IPV4_LOCALNETWORK )
		wprintf( L" IPV4_LOCALNETWORK" );
	if( connectivity & NLM_CONNECTIVITY_IPV4_INTERNET )
		wprintf( L" IPV4_INTERNET" );

	if( connectivity & NLM_CONNECTIVITY_IPV6_NOTRAFFIC )
		wprintf( L" IPV6_NOTRAFFIC" );
	if( connectivity & NLM_CONNECTIVITY_IPV6_SUBNET )
		wprintf( L" IPV6_SUBNET" );
	if( connectivity & NLM_CONNECTIVITY_IPV6_LOCALNETWORK )
		wprintf( L" IPV6_LOCALNETWORK" );
	if( connectivity & NLM_CONNECTIVITY_IPV6_INTERNET )
		wprintf( L" IPV6_INTERNET" );

	putwchar( '\n' );
}

void print_datetime( SYSTEMTIME datetime, wchar_t* prefix = L"" )
{
	wprintf_s( L"%s%4d-%02d-%02d %02d:%02d:%02d\n", prefix, datetime.wYear, datetime.wMonth, datetime.wDay,  datetime.wHour, datetime.wMinute, datetime.wSecond );
}

void nlmtool_network_t::print( )
{
	if( index != -1 )
		wprintf( L"               Index: %ld\n", index );

	wprintf( L"          Network id: %s\n", guid_string );

	print_connectivity( connectivity, L"        Connectivity:" );


	const wchar_t* category_string = L"unknown";

	switch( category )
	{
	case NLM_NETWORK_CATEGORY_PUBLIC:
		category_string = L"PUBLIC";
		break;
	case NLM_NETWORK_CATEGORY_PRIVATE:
		category_string = L"PRIVATE";
		break;
	case NLM_NETWORK_CATEGORY_DOMAIN_AUTHENTICATED:
		category_string = L"AUTHENTICATED";
		break;
	}

	wprintf_s( L"            Category: %s\n", category_string );


	const wchar_t* domain_type_string = L"unknown";

	switch( domain_type )
	{
	case NLM_DOMAIN_TYPE_NON_DOMAIN_NETWORK:
		domain_type_string = L"NON_DOMAIN_NETWORK";
		break;
	case NLM_DOMAIN_TYPE_DOMAIN_NETWORK:
		domain_type_string = L"DOMAIN_NETWORK";
		break;
	case NLM_DOMAIN_TYPE_DOMAIN_AUTHENTICATED:
		domain_type_string = L"DOMAIN_AUTHENTICATED";
		break;
	}

	wprintf( L"         Domain type: %s\n", domain_type_string );

	wprintf_s( L"                Name: %s\n", name );
	wprintf_s( L"         Description: %s\n", description );

	print_datetime( datetime_created, L"             Created: " );
	print_datetime( datetime_connected, L"           Connected: " );
}


void print_version( )
{
	wprintf_s( L"%s\n", WINDOWSNLMTOOL_VERSION_STRING );
	wprintf_s( L"For the documentation and new releases visit:\n%s\n\n", WINDOWSNLMTOOL_HOMEPAGE_STRING );
}

void print_help( wchar_t* progname )
{
	wprintf_s( L"Usage:\n" );
	wprintf_s( L"  %s [-option | -parameter <value>] [more options/parameters]\n\n", progname );
	wprintf_s(
		L"  Basically, it selects a (sub)set of networks from the Network List Manager\n"
		L"and prints them (it is possible to get an empty set); then it tries to apply\n"
		L"new parameters (category, name and description), if given, to the (sub)set\n"
		L"selected. Some operations may require elevation (Administrator's privileges),\n"
		L"and some are not possible at all on certain types of networks.\n"
		L"  Start by listing all networks, then supply parameters to list only network(s)\n"
		L"of interest; then apply new values to the (sub)set selected using one or more\n"
		L"of the '-setcategory', '-setname' and '-setdescription' commands.\n\n"
	);
	wprintf_s( L"  * * * Options / parameters are: * * *\n\n" );
	wprintf_s( L"  -help            prints this help\n\n" );
	wprintf_s( L"  * * * SELECTING network(s) by their properties * * *\n\n" );
	wprintf_s( L"  (no parameters)  selects all connected networks\n\n" );
	wprintf_s( L"  -first           selects the first network from the list\n\n" );
	wprintf_s( L"  -index N         selects Nth network (zero-based) from the list\n\n" );
	wprintf_s( L"  -networkid GUID  selects one network with the id given\n\n" );
	wprintf_s( L"  -ipv4 { NOTRAFFIC | SUBNET | LOCALNET | INTERNET | ANY }\n" );
	wprintf_s( L"                   selects network(s) with the ipv4 connectivity given\n\n" );
	wprintf_s( L"  -ipv6 { NOTRAFFIC | SUBNET | LOCALNET | INTERNET | ANY }\n" );
	wprintf_s( L"                   selects network(s) with the ipv6 connectivity given\n\n" );
	wprintf_s( L"                   If both -ipv4 and -ipv6 are given, there is a logical AND\n" );
	wprintf_s( L"                   between them: [-ipv4 ...] AND [-ipv6 ...]\n\n" );
	wprintf_s( L"  -disconnected    selects disconnected network(s);\n" );
	wprintf_s( L"                   can be used with -ipv4 and/or -ipv6, implies logical OR:\n" );
	wprintf_s( L"                   [-disconnected] OR [-ipv4 ...] AND [-ipv6 ...]\n\n" );
	wprintf_s( L"  -category { PUBLIC | PRIVATE | DOMAIN }\n" );
	wprintf_s( L"                   selects network(s) with the category given\n\n" );
	wprintf_s( L"  -domaintype { NONDOMAIN | DOMAIN | AUTHENTICATED }\n" );
	wprintf_s( L"                   selects network(s) with the domain type given\n\n" );
	wprintf_s( L"  -name <text>     selects network by name\n\n" );
	wprintf_s( L"  -description <text>\n" );
	wprintf_s( L"                   selects network by description\n\n" );
	wprintf_s( L"  * * * CHANGING network properties * * *\n\n" );
	wprintf_s( L"  -setcategory { PUBLIC | PRIVATE }\n" );
	wprintf_s( L"                   sets the category for ALL selected networks\n\n" );
	wprintf_s( L"  -setname <text>  sets the name for ALL selected networks\n\n" );
	wprintf_s( L"  -setdescription <text>\n" );
	wprintf_s( L"                   sets the description for ALL selected networks\n\n" );
}

int wmain( int argc, wchar_t* argv[ ] )
{
	enum RetCodes
	{
		RetOK,
		RetErr_CmdLine,
		RetErr_CoInit,
		RetErr_CoCreat,
		RetErr_NLM_GetNetworks,
		RetUnknown
	}
	RetCode = RetOK;


	print_version( );


	if( !nlmtool_params.init( argc - 1, argv + 1 ) )
	{
		fwprintf( stderr, L"Failed to parse the command line. Exiting now. Try '%s -help'.\n", argv[ 0 ] );

		return RetErr_CmdLine;
	}


	// ask for UNICODE support for our console:
	_setmode( _fileno( stdout ), _O_U16TEXT );


	if( nlmtool_params.help_requested )
	{
		print_help( argv[ 0 ] );

		return RetOK;
	}


	HRESULT hr = 0;

	hr = CoInitializeEx(
		nullptr,
		COINIT_APARTMENTTHREADED
	);

		if( FAILED( hr ) )
		{
			fwprintf( stderr, L"CoInitializeEx( ) failed with error code (%d). Exiting now.", hr );

			RetCode = RetErr_CoInit;

			goto Exit0;
		}

	INetworkListManager* network_list_manager = nullptr;

	hr = CoCreateInstance(
		CLSID_NetworkListManager,
		nullptr,
		CLSCTX_ALL,
		IID_INetworkListManager,
		( LPVOID* ) &network_list_manager
	);

		if( hr != S_OK )
		{
			fwprintf( stderr, L"CoCreateInstance( ) failed with error code (%d). Exiting now.\n", hr );

			RetCode = RetErr_CoCreat;

			goto Exit1;
		}


	if( nlmtool_params.print_overall_connectivity )
	{
		NLM_CONNECTIVITY overall_connectivity;

		hr = network_list_manager->GetConnectivity( &overall_connectivity );

		if( hr == S_OK )
			print_connectivity( overall_connectivity, L"Overall connectivity:" );
		else
			fwprintf( stderr, L"INetworkListManager::GetConnectivity( ) failed with error code (%d). Trying to continue...\n", hr );
	}


	IEnumNetworks* networks = nullptr;

	hr = network_list_manager->GetNetworks( NLM_ENUM_NETWORK_ALL, &networks );

		if( hr != S_OK )
		{
			fwprintf( stderr, L"INetworkListManager::GetNetworks( ) failed with error code (%d). Exiting now.\n", hr );

			RetCode = RetErr_NLM_GetNetworks;

			goto Exit2;
		}


	wprintf( L"============ Now let's try to enumerate networks ============\n" );

	long list_network_index = 0, total_network_index = 0;

	while( true )
	{
		INetwork* network = nullptr;

		hr = networks->Next( 1, &network, nullptr );

			if( hr != S_OK )
				break;


		nlmtool_network_t nlmtool_network;

		if( !nlmtool_network.init( network, total_network_index ) )
		{
			fwprintf_s( stderr, L"FAILED to get network connection's properties!\n" );

			goto EndWhile1;
		}


		// filter out non-conformant connections (according to the command-line):

		if( nlmtool_params.network.first )
			if( 0 != list_network_index )
				goto EndWhile1;

		if( nlmtool_params.network.index != -1 )
			if( nlmtool_params.network.index != total_network_index )
				goto EndWhile1;

		if( nlmtool_params.network.guid_arg )
			if( !IsEqualGUID( nlmtool_params.network.guid, nlmtool_network.guid ) )
				goto EndWhile1;

//		if( ! ( nlmtool_params.network.connectivity & nlmtool_network.connectivity ) )
//			goto EndWhile1;

		if(
			! (
				( nlmtool_params.network.connectivity & IPv4Connectivity & nlmtool_network.connectivity ) &&
				( nlmtool_params.network.connectivity & IPv6Connectivity & nlmtool_network.connectivity ) ||
				( nlmtool_params.network.connectivity & ConnectivityDisconnected & nlmtool_network.connectivity )
			)
		)
			goto EndWhile1;

		if( nlmtool_params.network.category_set )
			if( nlmtool_params.network.category != nlmtool_network.category )
				goto EndWhile1;

		if( nlmtool_params.network.domain_type_set )
			if( nlmtool_params.network.domain_type != nlmtool_network.domain_type )
				goto EndWhile1;

		if( nlmtool_params.network.name )
			if( _wcsicmp( nlmtool_params.network.name, nlmtool_network.name ) != 0 )
				goto EndWhile1;

		if( nlmtool_params.network.description )
			if( _wcsicmp( nlmtool_params.network.description, nlmtool_network.description ) != 0 )
				goto EndWhile1;


		// print network's properties:

		if( list_network_index > 0 )
			wprintf( L"-------------------------------------------------------------\n" );

		nlmtool_network.print( );


		// set new properties (if given):

		if( nlmtool_params.network.new_category_set )
			if( nlmtool_network.set_category( nlmtool_params.network.new_category ) )
			{
				const wchar_t* new_category_string = L"";

				if( nlmtool_params.network.new_category == NLM_NETWORK_CATEGORY_PUBLIC )
					new_category_string = L"PUBLIC";
				else if( nlmtool_params.network.new_category == NLM_NETWORK_CATEGORY_PRIVATE)
					new_category_string = L"PRIVATE";

				wprintf_s( L"    New category SET: %s\n", new_category_string );
			}
			else
				fwprintf_s( stderr, L"FAILED to set new network category!\n" );

		if( nlmtool_params.network.new_name )
			if( nlmtool_network.set_name( nlmtool_params.network.new_name ) )
				wprintf_s( L"        New name SET: %s\n", nlmtool_params.network.new_name );
			else
				fwprintf_s( stderr, L"FAILED to set new network name!\n" );

		if( nlmtool_params.network.new_description )
			if( nlmtool_network.set_descripiton( nlmtool_params.network.new_description ) )
				wprintf_s( L" New description SET: %s\n", nlmtool_params.network.new_description );
			else
				fwprintf_s( stderr, L"FAILED to set new network description!\n" );


		++list_network_index;
EndWhile1:
		++total_network_index;
	}

	if( list_network_index == 0 )
		wprintf( L"================== No (suitable) networks! ==================\n" );
	else
		wprintf( L"================ No more (suitable) networks ================\n" );

	wprintf( L"     Listed networks: %ld\n", list_network_index );
	wprintf( L"      Total networks: %ld\n", total_network_index );

Exit2:
	network_list_manager->Release( );
Exit1:
	CoUninitialize( );
Exit0:

	return RetCode;
}
